import { Prisma, PrismaClient, UserTier, WithdrawStatus, WithdrawType } from '@prisma/client'

type DistributionCategory = 'DAILY' | 'BONUS' | 'UNKNOWN'

interface CliOptions {
  userIds: number[] | null
  idNumbers: string[] | null
  apply: boolean
  includeBonus: boolean
  verbose: boolean
}

interface RewardSnapshot {
  distributionKey: string | null
  category: DistributionCategory
  amountNear: number
  amountKrw: number
  stakedAmount: number
  dailyRate: number
  nearPrice: number
  settlementDate: Date
  createdAt: Date
}

interface BalanceEvent {
  type: 'DEPOSIT_CONFIRMED' | 'REWARD' | 'WITHDRAW_REQUEST' | 'WITHDRAW_CONFIRMED' | 'WITHDRAW_CANCELLED'
  amount: number
  at: Date
  withdrawType?: WithdrawType
  meta?: string
}

interface ProcessResult {
  userId: number
  idNumber: string
  name: string
  tier: UserTier
  totalRewardsNear: number
  totalRewardsKrw: number
  excludedBonusNear: number
  excludedBonusCount: number
  orphanBonusNear: number
  orphanBonusCount: number
  expectedBalance: number
  expectedTotal: number
  expectedFrozen: number
  currentBalance: number
  currentTotal: number
  currentFrozen: number
  rewardCount: number
  settlementCount: number
  applied: boolean
  messages: string[]
}

const prisma = new PrismaClient()

const EVENT_ORDER: Record<BalanceEvent['type'], number> = {
  DEPOSIT_CONFIRMED: 1,
  REWARD: 2,
  WITHDRAW_REQUEST: 3,
  WITHDRAW_CANCELLED: 4,
  WITHDRAW_CONFIRMED: 5
}

const classifyDistribution = (distributionKey: string | null): DistributionCategory => {
  if (!distributionKey) {
    return 'UNKNOWN'
  }
  if (distributionKey.startsWith('daily:')) {
    return 'DAILY'
  }
  if (distributionKey.startsWith('bonus:')) {
    return 'BONUS'
  }
  return 'UNKNOWN'
}

const parseArgs = (): CliOptions => {
  const options: CliOptions = {
    userIds: null,
    idNumbers: null,
    apply: false,
    includeBonus: false,
    verbose: false
  }

  for (const arg of process.argv.slice(2)) {
    if (arg === '--apply') {
      options.apply = true
    } else if (arg === '--include-bonus') {
      options.includeBonus = true
    } else if (arg === '--verbose') {
      options.verbose = true
    } else if (arg.startsWith('--userId=')) {
      const value = arg.replace('--userId=', '').trim()
      const ids = value.split(',').map(item => Number(item.trim())).filter(id => !Number.isNaN(id))
      if (ids.length === 0) {
        throw new Error('无效的 --userId 参数，至少需要一个有效的数字ID')
      }
      options.userIds = ids
    } else if (arg.startsWith('--idNumber=')) {
      const value = arg.replace('--idNumber=', '').trim()
      const ids = value.split(',').map(item => item.trim()).filter(item => item.length > 0)
      if (ids.length === 0) {
        throw new Error('无效的 --idNumber 参数，至少需要一个有效的身份证号')
      }
      options.idNumbers = ids
    } else if (arg === '--all') {
      options.userIds = null
      options.idNumbers = null
    } else {
      throw new Error(`未知参数: ${arg}`)
    }
  }

  return options
}

const formatDecimal = (value: unknown): number => {
  if (value === null || value === undefined) {
    return 0
  }
  if (typeof value === 'number') {
    return value
  }
  if (typeof value === 'bigint') {
    return Number(value)
  }
  if (typeof value === 'object') {
    const decimalLike = value as { toNumber?: () => number }
    if (typeof decimalLike.toNumber === 'function') {
      return decimalLike.toNumber()
    }
  }
  return Number(value)
}

const roundKrw = (value: number): number => Math.round(value * 100) / 100
const roundNear = (value: number): number => Math.round(value * 10000) / 10000

const toDecimal = (value: number, fractionDigits: number): Prisma.Decimal => {
  const factor = 10 ** fractionDigits
  const rounded = Math.round(value * factor) / factor
  return new Prisma.Decimal(rounded)
}

const buildDistributionTxHash = (distributionKey: string | null): string | null => {
  if (!distributionKey) {
    return null
  }
  const normalized = distributionKey.replace(/[^a-zA-Z0-9]+/g, '_')
  return `auto_distribution_${normalized}`
}

const collectRewardSnapshots = (
  settlements: Array<{ distributionKey: string | null; rewardNear: Prisma.Decimal; rewardKrw: Prisma.Decimal; stakedAmount: Prisma.Decimal; dailyRewardRate: Prisma.Decimal; nearPrice: Prisma.Decimal; settlementDate: Date }>,
  rewards: Array<{ distributionKey: string | null; amount: Prisma.Decimal; createdAt: Date }>
): RewardSnapshot[] => {
  const rewardMap = new Map<string, Date>()
  for (const reward of rewards) {
    const key = reward.distributionKey ?? '__NO_KEY__'
    if (!rewardMap.has(key)) {
      rewardMap.set(key, reward.createdAt)
    }
  }

  return settlements.map(settlement => {
    const key = settlement.distributionKey ?? '__NO_KEY__'
    const category = classifyDistribution(settlement.distributionKey)
    const createdAt = rewardMap.get(key) ?? settlement.settlementDate

    return {
      distributionKey: settlement.distributionKey,
      category,
      amountNear: roundNear(formatDecimal(settlement.rewardNear)),
      amountKrw: roundKrw(formatDecimal(settlement.rewardKrw)),
      stakedAmount: roundNear(formatDecimal(settlement.stakedAmount)),
      dailyRate: formatDecimal(settlement.dailyRewardRate),
      nearPrice: roundKrw(formatDecimal(settlement.nearPrice)),
      settlementDate: settlement.settlementDate,
      createdAt
    }
  })
}

const collectBalanceEvents = async (userId: number, rewardSnapshots: RewardSnapshot[]): Promise<BalanceEvent[]> => {
  const events: BalanceEvent[] = []

  const deposits = await prisma.depositRecord.findMany({
    where: { userId, status: 'CONFIRMED' },
    select: { amount: true, processedAt: true, createdAt: true }
  })

  for (const deposit of deposits) {
    const amount = roundKrw(formatDecimal(deposit.amount))
    const at = deposit.processedAt ?? deposit.createdAt
    events.push({
      type: 'DEPOSIT_CONFIRMED',
      amount,
      at,
      meta: '充值到账'
    })
  }

  for (const reward of rewardSnapshots) {
    events.push({
      type: 'REWARD',
      amount: reward.amountKrw,
      at: reward.settlementDate,
      meta: reward.distributionKey ?? undefined
    })
  }

  const withdraws = await prisma.withdrawRecord.findMany({
    where: { userId },
    select: {
      amount: true,
      status: true,
      withdrawType: true,
      createdAt: true,
      processedAt: true
    }
  })

  for (const withdraw of withdraws) {
    const amount = roundKrw(formatDecimal(withdraw.amount))
    const createdAt = withdraw.createdAt
    events.push({
      type: 'WITHDRAW_REQUEST',
      amount,
      at: createdAt,
      withdrawType: withdraw.withdrawType
    })

    if (withdraw.status === WithdrawStatus.CONFIRMED) {
      const processedAt = withdraw.processedAt ?? createdAt
      events.push({
        type: 'WITHDRAW_CONFIRMED',
        amount,
        at: processedAt,
        withdrawType: withdraw.withdrawType
      })
    } else if (withdraw.status === WithdrawStatus.FAILED || withdraw.status === WithdrawStatus.CANCELLED) {
      const processedAt = withdraw.processedAt ?? createdAt
      events.push({
        type: 'WITHDRAW_CANCELLED',
        amount,
        at: processedAt,
        withdrawType: withdraw.withdrawType
      })
    }
  }

  return events
}

const simulateBalanceFlow = (events: BalanceEvent[]): { balance: number; frozen: number; total: number } => {
  const sorted = events.sort((a, b) => {
    const timeDiff = a.at.getTime() - b.at.getTime()
    if (timeDiff !== 0) {
      return timeDiff
    }
    return EVENT_ORDER[a.type] - EVENT_ORDER[b.type]
  })

  let balance = 0
  let frozen = 0
  let total = 0

  for (const event of sorted) {
    switch (event.type) {
      case 'DEPOSIT_CONFIRMED':
        balance += event.amount
        total += event.amount
        break
      case 'REWARD':
        balance += event.amount
        total += event.amount
        break
      case 'WITHDRAW_REQUEST':
        if (event.withdrawType === WithdrawType.BALANCE) {
          balance -= event.amount
          frozen += event.amount
        }
        break
      case 'WITHDRAW_CONFIRMED':
        if (event.withdrawType === WithdrawType.BALANCE) {
          frozen -= event.amount
          total -= event.amount
        }
        break
      case 'WITHDRAW_CANCELLED':
        if (event.withdrawType === WithdrawType.BALANCE) {
          balance += event.amount
          frozen -= event.amount
        }
        break
      default:
        break
    }
  }

  return {
    balance: roundKrw(balance),
    frozen: roundKrw(frozen),
    total: roundKrw(total)
  }
}

const ensureUserBalance = async (
  tx: Prisma.TransactionClient,
  userId: number,
  targetBalance: number,
  targetFrozen: number,
  targetTotal: number
): Promise<void> => {
  const existing = await tx.userBalance.findUnique({ where: { userId } })
  if (existing) {
    await tx.userBalance.update({
      where: { userId },
      data: {
        balance: toDecimal(targetBalance, 2),
        frozen: toDecimal(targetFrozen, 2),
        total: toDecimal(targetTotal, 2)
      }
    })
  } else {
    await tx.userBalance.create({
      data: {
        userId,
        balance: toDecimal(targetBalance, 2),
        frozen: toDecimal(targetFrozen, 2),
        total: toDecimal(targetTotal, 2)
      }
    })
  }
}

const processSingleUser = async (userId: number, options: CliOptions): Promise<ProcessResult | null> => {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: {
      idNumber: true,
      name: true,
      tier: true
    }
  })

  if (!user) {
    console.warn(`用户 ${userId} 不存在，跳过`)
    return null
  }

  const staking = await prisma.userStaking.findUnique({
    where: { userId },
    select: {
      totalRewardsNear: true
    }
  })

  if (!staking) {
    console.warn(`用户 ${userId} 没有质押记录，跳过`)
    return null
  }

  const rewards = await prisma.stakingReward.findMany({
    where: { userId },
    select: {
      distributionKey: true,
      amount: true,
      createdAt: true
    },
    orderBy: { createdAt: 'asc' }
  })

  const settlements = await prisma.stakingSettlement.findMany({
    where: { userId },
    select: {
      distributionKey: true,
      rewardNear: true,
      rewardKrw: true,
      stakedAmount: true,
      dailyRewardRate: true,
      nearPrice: true,
      settlementDate: true
    },
    orderBy: { settlementDate: 'asc' }
  })

  const rewardSnapshots = collectRewardSnapshots(settlements, rewards)

  const includedRewards = rewardSnapshots.filter(snapshot => {
    if (snapshot.category === 'BONUS') {
      return options.includeBonus
    }
    return true
  })

  const excludedBonus = rewardSnapshots.filter(snapshot => snapshot.category === 'BONUS' && !options.includeBonus)

  const bonusRewardsWithoutSettlement = rewards.filter(reward => {
    if (classifyDistribution(reward.distributionKey) !== 'BONUS') {
      return false
    }
    const key = reward.distributionKey ?? null
    return !rewardSnapshots.some(snapshot => snapshot.distributionKey === key)
  })

  const totalRewardsNear = roundNear(includedRewards.reduce((sum, item) => sum + item.amountNear, 0))
  const totalRewardsKrw = roundKrw(includedRewards.reduce((sum, item) => sum + item.amountKrw, 0))
  const excludedBonusNear = roundNear(excludedBonus.reduce((sum, item) => sum + item.amountNear, 0))
  const orphanBonusNear = roundNear(bonusRewardsWithoutSettlement.reduce((sum, reward) => sum + formatDecimal(reward.amount), 0))
  const orphanBonusCount = bonusRewardsWithoutSettlement.length

  const events = await collectBalanceEvents(userId, includedRewards)
  const balances = simulateBalanceFlow(events)

  const currentBalanceRecord = await prisma.userBalance.findUnique({
    where: { userId },
    select: {
      balance: true,
      frozen: true,
      total: true
    }
  })

  const result: ProcessResult = {
    userId,
    idNumber: user.idNumber,
    name: user.name,
    tier: user.tier,
    totalRewardsNear,
    totalRewardsKrw,
    excludedBonusNear,
    excludedBonusCount: excludedBonus.length,
    orphanBonusNear,
    orphanBonusCount,
    expectedBalance: balances.balance,
    expectedTotal: balances.total,
    expectedFrozen: balances.frozen,
    currentBalance: currentBalanceRecord ? roundKrw(formatDecimal(currentBalanceRecord.balance)) : 0,
    currentTotal: currentBalanceRecord ? roundKrw(formatDecimal(currentBalanceRecord.total)) : 0,
    currentFrozen: currentBalanceRecord ? roundKrw(formatDecimal(currentBalanceRecord.frozen)) : 0,
    rewardCount: rewards.length,
    settlementCount: settlements.length,
    applied: false,
    messages: []
  }

  if (options.apply) {
    await prisma.$transaction(async tx => {
      await tx.stakingReward.deleteMany({ where: { userId } })
      await tx.stakingSettlement.deleteMany({ where: { userId } })

      if (includedRewards.length > 0) {
        await tx.stakingSettlement.createMany({
          data: includedRewards.map(snapshot => ({
            userId,
            distributionKey: snapshot.distributionKey,
            rewardNear: toDecimal(snapshot.amountNear, 4),
            rewardKrw: toDecimal(snapshot.amountKrw, 2),
            stakedAmount: toDecimal(snapshot.stakedAmount, 4),
            dailyRewardRate: toDecimal(snapshot.dailyRate, 6),
            nearPrice: toDecimal(snapshot.nearPrice, 2),
            settlementDate: snapshot.settlementDate
          }))
        })

        await tx.stakingReward.createMany({
          data: includedRewards.map(snapshot => ({
            userId,
            amount: toDecimal(snapshot.amountNear, 4),
            distributionKey: snapshot.distributionKey,
            txHash: buildDistributionTxHash(snapshot.distributionKey),
            createdAt: snapshot.createdAt
          }))
        })
      }

      await tx.userStaking.update({
        where: { userId },
        data: {
          totalRewardsNear: toDecimal(totalRewardsNear, 4)
        }
      })

      await ensureUserBalance(tx, userId, balances.balance, balances.frozen, balances.total)
    })

    result.applied = true
    if (result.excludedBonusNear > 0) {
      result.messages.push(`已剔除补发奖励 ${result.excludedBonusNear.toFixed(4)} NEAR（${result.excludedBonusCount} 条）`)
    }
    if (result.orphanBonusNear > 0 && !options.includeBonus) {
      result.messages.push(`已移除缺少结算记录的补发奖励 ${result.orphanBonusNear.toFixed(4)} NEAR（${result.orphanBonusCount} 条）`)
    }
    result.messages.push(`已重写质押奖励 ${includedRewards.length} 条，余额同步为 ₩${balances.balance.toFixed(2)}`)
  } else {
    if (result.excludedBonusNear > 0 && !options.includeBonus) {
      result.messages.push(`预览：将排除补发奖励 ${result.excludedBonusNear.toFixed(4)} NEAR（${result.excludedBonusCount} 条）`)
    }
    if (result.orphanBonusNear > 0 && !options.includeBonus) {
      result.messages.push(`预览：将移除缺少结算记录的补发奖励 ${result.orphanBonusNear.toFixed(4)} NEAR（${result.orphanBonusCount} 条）`)
    }
    result.messages.push(`预览：质押奖励将重写为 ${includedRewards.length} 条`)
  }

  return result
}

const main = async (): Promise<void> => {
  const options = parseArgs()

  const userIdSet = new Set<number>()

  if (options.userIds && options.userIds.length > 0) {
    options.userIds.forEach(id => userIdSet.add(id))
  }

  if (options.idNumbers && options.idNumbers.length > 0) {
    const users = await prisma.user.findMany({
      where: { idNumber: { in: options.idNumbers } },
      select: { id: true, idNumber: true }
    })

    const foundMap = new Map<string, number>()
    users.forEach(user => foundMap.set(user.idNumber, user.id))

    for (const idNumber of options.idNumbers) {
      const resolved = foundMap.get(idNumber)
      if (resolved) {
        userIdSet.add(resolved)
      } else {
        console.warn(`⚠️ 身份证号 ${idNumber} 未找到对应用户，已跳过`)
      }
    }
  }

  if (userIdSet.size === 0) {
    const stakingUsers = await prisma.userStaking.findMany({
      where: {
        OR: [
          { stakedAmount: { gt: 0 } },
          { totalRewardsNear: { gt: 0 } }
        ]
      },
      select: { userId: true }
    })
    stakingUsers.forEach(item => userIdSet.add(item.userId))
  }

  const userIds = Array.from(userIdSet)

  if (userIds.length === 0) {
    console.log('没有需要处理的用户。')
    return
  }

  const results: ProcessResult[] = []

  for (const userId of userIds) {
    try {
      const result = await processSingleUser(userId, options)
      if (result) {
        results.push(result)

        if (options.verbose) {
          console.log(JSON.stringify(result, null, 2))
        }
      }
    } catch (error) {
      console.error(`处理用户 ${userId} 失败:`, error)
    }
  }

  const summary = {
    processedUsers: results.length,
    appliedCount: results.filter(item => item.applied).length,
    includeBonus: options.includeBonus,
    applyMode: options.apply,
    results: results.map(item => ({
      userId: item.userId,
      idNumber: item.idNumber,
      name: item.name,
      tier: item.tier,
      totalRewardsNear: item.totalRewardsNear,
      totalRewardsKrw: item.totalRewardsKrw,
      excludedBonusNear: item.excludedBonusNear,
       excludedBonusCount: item.excludedBonusCount,
       orphanBonusNear: item.orphanBonusNear,
       orphanBonusCount: item.orphanBonusCount,
      expectedBalance: item.expectedBalance,
      expectedTotal: item.expectedTotal,
      expectedFrozen: item.expectedFrozen,
      currentBalance: item.currentBalance,
      currentTotal: item.currentTotal,
      currentFrozen: item.currentFrozen,
      applied: item.applied,
      messages: item.messages
    }))
  }

  console.log(JSON.stringify(summary, null, 2))
}

main().catch(async error => {
  console.error('收益重算脚本执行失败:', error)
  await prisma.$disconnect()
  process.exit(1)
}).finally(async () => {
  await prisma.$disconnect()
})
